/*
 * University of British Columbia
 * Department of Computer Science
 * CPSC317 - Internet Programming
 * Assignment 1
 * 
 * Author: 
 * January 2012
 * 
 * This code may not be used without written consent of the authors, except for 
 * current and future projects and assignments of the CPSC317 course at UBC.
 */

package ubc.cics515.xmpp.net;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Date;

import javax.xml.bind.DatatypeConverter;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import ubc.cics515.xmpp.exception.XMPPException;
import ubc.cics515.xmpp.model.Contact;
import ubc.cics515.xmpp.model.ContactStatus;
import ubc.cics515.xmpp.model.Message;
import ubc.cics515.xmpp.model.Session;

/**
 * This class describes the XMPP connection handler. A socket connection is
 * created when an instance of this handler is created, and methods are provided
 * for most common operations in XMPP.
 * 
 * This class will not in any case make a direct reference to any class or
 * method that represents a specific UI library.
 */
public class XMPPConnection {

	/**
	 * our variables
	 */
	private boolean hasLOGIN;
	private boolean hasPLAIN;
	private boolean isAuthenticated;
	private String domain;

	/**
	 * Default TCP port for client-server communication in XMPP.
	 */
	public static final int XMPP_DEFAULT_PORT = 5222;

	/**
	 * Session object for communication between the network component and the
	 * chat model and UI.
	 */
	private Session session;

	/**
	 * Socket object associated to the communication between this client and the
	 * XMPP server.
	 */
	private Socket socket;

	/**
	 * XMPP reader helper, used to obtain XML nodes from the XMPP stream.
	 */
	// Variable xmppReader is unused in provided code, but you should use it in
	// your code. Once that is done, the @SuppressWarnings can be removed.
	@SuppressWarnings("unused")
	private XMPPStreamReader xmppReader;

	/**
	 * XMPP writer helper, used to write XML nodes to the XMPP stream.
	 */
	// Variable xmppWriter is unused in provided code, but you should use it in
	// your code. Once that is done, the @SuppressWarnings can be removed.
	@SuppressWarnings("unused")
	private XMPPStreamWriter xmppWriter;

	/**
	 * Creates a new instance of the connection handler. This constructor will
	 * creating the socket, initialise the reader and writer helpers, send
	 * initial tags, authenticate the user and bind to a resource.
	 * 
	 * @param jidUser
	 *            User part of the Jabber ID.
	 * @param jidDomain
	 *            Domain part of the Jabber ID.
	 * @param resource
	 *            Resource to bind once authenticated. If null or empty, a new
	 *            resource will be generated.
	 * @param password
	 *            Password for authentication.
	 * @param session
	 *            Instance of the session to communicate with other parts of the
	 *            system.
	 * @throws XMPPException
	 *             If there is an error establishing the connection, sending or
	 *             receiving necessary data, or while authenticating.
	 */
	public XMPPConnection(String jidUser, String jidDomain, String resource,
			String password, Session session) throws XMPPException {

		// initialize our variables
		this.hasLOGIN = false;
		this.hasPLAIN = false;
		this.isAuthenticated = false;
		this.domain = jidDomain;

		this.session = session;

		initializeConnection(jidDomain);

		try {
			xmppReader = new XMPPStreamReader(socket.getInputStream());
			xmppWriter = new XMPPStreamWriter(socket.getOutputStream());
		} catch (XMPPException e) {
			throw e;
		} catch (Exception e) {
			throw new XMPPException("Could not obtain socket I/O channels ("
					+ e.getMessage() + ")", e);
		}

		initializeStreamAndFeatures(jidUser, jidDomain);

		login(jidUser, password);

		bindResource(resource);

		startListeningThread();
	}

	/**
	 * Initialises the connection with the specified domain. This method sets
	 * the socket field with an initialised socket.
	 * 
	 * @param domain
	 *            DNS name (or IP string) of the server to connect to.
	 * @throws XMPPException
	 *             If there is a problem connecting to the server.
	 */
	private void initializeConnection(String domain) throws XMPPException {

		/*
		 * YOUR CODE HERE hints: for error, throw new XMPPEXception ("Doesn't",
		 * e)
		 */
		try {
			socket = new Socket(domain, XMPP_DEFAULT_PORT);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			throw new XMPPException("Cannot create socket", e);
		}

	}

	/**
	 * Sends the initial data to establish an XMPP connection stream with the
	 * XMPP server. This method also retrieves the set of features from the
	 * server, saving it in a field for future use.
	 * 
	 * @param jidUser
	 *            User part of the Jabber ID.
	 * @param jidDomain
	 *            Domain part of the Jabber ID.
	 * @throws XMPPException
	 *             If there is a problem sending or receiving the data.
	 */
	private void initializeStreamAndFeatures(String jidUser, String jidDomain)
			throws XMPPException {

		/*
		 * YOUR CODE HERE hints: -don't send </stream:>, cause it is used to
		 * close connection - receive <streamfeature> <mechanisms>
		 * <mechanism>Login</mechanism> have to check this mechanism - only do
		 * one mechanism
		 */
		Element rootElement = xmppWriter.createRootElement("stream:stream");
		rootElement.setAttribute("from", jidUser + "@" + jidDomain);
		rootElement.setAttribute("to", jidDomain);
		rootElement.setAttribute("version", "1.0");
		rootElement.setAttribute("xml:lang", "en");
		rootElement.setAttribute("xmlns", "jabber:client");
		rootElement.setAttribute("xmlns:stream",
				"http://etherx.jabber.org/streams");
		xmppWriter.writeRootElementWithoutClosingTag();

		Element response = xmppReader.readSecondLevelElement();
		NodeList mechanisms = response.getElementsByTagName("mechanism");

		for (int i = 0; i <= mechanisms.getLength() - 1; i++) {
			if (mechanisms.item(i).getTextContent().equals("LOGIN")) {
				this.hasLOGIN = true;
			}
			if (mechanisms.item(i).getTextContent().equals("PLAIN")) {
				this.hasPLAIN = true;
			}

		}

		if ((!this.hasLOGIN) && (!this.hasPLAIN))
			throw new XMPPException(
					"Server does not support SASL mechanisms: LOGIN or PLAIN");
	}

	/**
	 * Attempts to authenticate the user name with the provided password at the
	 * connected server. This method will verify if the server supports the
	 * implemented authentication mechanism(s) and send the user and password
	 * based on the first mechanism it finds. In case authentication is not
	 * successful, this function will throw an XMPPException. This function also
	 * retrieves the new set of features available after authentication.
	 * 
	 * @param username
	 *            User name to use for authentication.
	 * @param password
	 *            Password to use for authentication.
	 * @throws XMPPException
	 *             If authentication is not successful, or if authentication
	 *             methods supported by the server are not implemented, or if
	 *             there was a problem sending authentication data.
	 */
	private void login(String username, String password) throws XMPPException {

		/*
		 * YOUR CODE HERE hints: - <auth mechanism = LOGIN ```> some
		 * string(base-64 encoding) </auth> - check success and failure
		 * (<success /> <failure/>) - first send equal mark in 'some string',
		 * server reply challenge, then send user name and password, and then
		 * server reply success or failure or another challenge
		 */
		if (this.hasPLAIN) {
			Element initiation = xmppWriter.createElement("auth");
			initiation
					.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
			initiation.setAttribute("mechanism", "PLAIN");
			initiation.setTextContent("");

			System.out.println();
			// xmppWriter.debugElement(System.out, initiation);
			xmppWriter.writeIndividualElement(initiation);

			Element challenge = xmppReader.readSecondLevelElement();
			xmppWriter.debugElement(System.out, challenge);
			System.out.println(" ");

			/*
			 * construct login information string
			 */
			if (username == null || username.isEmpty())
				throw new XMPPException("User Name cannot be empty ! ");

			if (password == null || password.isEmpty())
				throw new XMPPException("Password Cannot be empty!");

			byte[] name = username.getBytes();
			byte[] pwd = password.getBytes();
			byte[] authentication = new byte[name.length + 2 + pwd.length];
			System.arraycopy(name, 0, authentication, 1, name.length);
			System.arraycopy(pwd, 0, authentication, 1 + name.length + 1,
					pwd.length);

			String loginMessage = DatatypeConverter
					.printBase64Binary(authentication);

			initiation = xmppWriter.createElement("response");
			initiation
					.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
			initiation.setTextContent(loginMessage);
			xmppWriter.debugElement(System.out, initiation);
			xmppWriter.writeIndividualElement(initiation);

			challenge = xmppReader.readSecondLevelElement();
			xmppWriter.debugElement(System.out, challenge);
			System.out.println();

			if (challenge.getTagName().equals("failure"))
				throw new XMPPException(
						"Fail to login, invalid account name/password!");
			else if (challenge.getTagName().equals("success")) {
				Element rootElement = xmppWriter.createElement("stream:stream");
				rootElement.setAttribute("from", username + "@" + this.domain);
				rootElement.setAttribute("to", this.domain);
				rootElement.setAttribute("version", "1.0");
				rootElement.setAttribute("xml:lang", "en");
				rootElement.setAttribute("xmlns", "jabber:client");
				rootElement.setAttribute("xmlns:stream",
						"http://etherx.jabber.org/streams");
				xmppWriter.writeRootElementWithoutClosingTag();
				this.isAuthenticated = true;
				xmppWriter.debugElement(System.out, rootElement);
			} else
				throw new XMPPException(
						"Something is wrong! debug message, u should not see this");

		} else {
			throw new XMPPException("PLAIN mechanism is not supported! ");
		}

	}

	/**
	 * Binds the connection to a specific resource, or retrieves a
	 * server-generated resource if one is not provided. This function will wait
	 * until a resource is sent by the server.
	 * 
	 * @param resource
	 *            Name of the user-specified resource. If resource is null or
	 *            empty, retrieves a server-generated one.
	 * @throws XMPPException
	 *             If there is an error sending or receiving the data.
	 */
	private void bindResource(String resource) throws XMPPException {

		/*
		 * YOUR CODE HERE hints: multiple login for the same account, ask the
		 * server for resource identification -check resource before send
		 * resource, since the resource probably is already there -send <iq -you
		 * have to test this
		 */

		if (!this.isAuthenticated)
			throw new XMPPException("Login failed, cannot bind resource!");

		Element bindElement = this.xmppReader.readSecondLevelElement();
		NodeList bindList = bindElement.getElementsByTagName("bind");
		if (bindList.getLength() <= 0) {
			throw new XMPPException("Server does not support resource binding!");
		}

		if (resource == null || resource.length() <= 0) // client does not
														// provide resource
		{
			Element iqElement = this.xmppWriter.createElement("iq");
			iqElement.setAttribute("type", "set");
			bindElement = this.xmppWriter.createElement("bind");
			bindElement.setAttribute("xmlns",
					"urn:ietf:params:xml:ns:xmpp-bind");
			iqElement.appendChild(bindElement);

			System.out.println();
			this.xmppWriter.debugElement(System.out, iqElement);
			this.xmppWriter.writeIndividualElement(iqElement);

			NodeList jidList = this.xmppReader.readSecondLevelElement()
					.getElementsByTagName("jid");
			if (jidList.getLength() <= 0)
				throw new XMPPException("Failed to bind resource!");
			else {
				String jidString = jidList.item(0).getTextContent();
				int i;
				for (i = 0; i <= jidString.length() - 1; i++) {
					if (jidString.charAt(i) != '/')
						continue;
					else {
						i++;
						break;
					}
				}
				if (i <= jidString.length())
					resource = jidString.substring(i);
				System.out.println();
				System.out.println(resource);
			}
		} else // client does provide a resource
		{
			Element iqElement = this.xmppWriter.createElement("iq");
			iqElement.setAttribute("type", "set");
			bindElement = this.xmppWriter.createElement("bind");
			bindElement.setAttribute("xmlns",
					"urn:ietf:params:xml:ns:xmpp-bind");
			Element resourceElement = this.xmppWriter.createElement("resource");
			resourceElement.setTextContent(resource);
			bindElement.appendChild(resourceElement);
			iqElement.appendChild(bindElement);

			this.xmppWriter.writeIndividualElement(iqElement);

			Element responseElement = this.xmppReader.readSecondLevelElement();

			NodeList jidList = responseElement.getElementsByTagName("jid");

			if (jidList.getLength() <= 0)
				throw new XMPPException("Failed to bind resource!");
			else {
				String jidString = jidList.item(0).getTextContent();
				int i;
				for (i = 0; i <= jidString.length() - 1; i++) {
					if (jidString.charAt(i) != '/')
						continue;
					else {
						i++;
						break;
					}
				}
				if (i <= jidString.length())
					resource = jidString.substring(i);
				System.out.println(resource);
			}

		}

	}

	/**
	 * Starts a thread that will keep listening for new messages asynchronously
	 * from the main thread.
	 */
	private void startListeningThread() {
		Thread listeningThread = new Thread(new Runnable() {

			@Override
			public void run() {
				listeningProcess();
			}
		});
		listeningThread.start();
	}

	/**
	 * Keeps listening for new XML elements in a loop until a closing tag is
	 * found or an exception happens. If an exception happens, calls
	 * <code>session.processReceivedException</code> and closes the connection.
	 * If the closing tag is found, sends a closing tag back and closes the
	 * connection as well. In both cases, the connection is closed at the
	 * session level, so that the model and UI can handle the connection being
	 * closed. For each received element, processes the element and handles it
	 * accordingly.
	 */
	private void listeningProcess() {
		try 
		{
			Element receivedElement = this.xmppReader.readSecondLevelElement();
			// this.xmppWriter.debugElement(System.out, receivedElement);
			while (true) 
			{
				if (this.xmppReader.isBaseDocument(receivedElement)) 
				{
					System.out.println("Server closes the connection!!");
					this.session.closeConnection();
					break;
				}

				System.out.println();
				this.xmppWriter.debugElement(System.out, receivedElement);
				String tagName = receivedElement.getTagName();

				if (tagName.equals("message")) 
				{
					String senderFullJid = receivedElement.getAttribute("from");
					String senderBareJid;
					String senderResource;
					
					if (senderFullJid.contains("/"))
					{
						senderBareJid = senderFullJid.split("/")[0];
						senderResource = senderFullJid.split("/")[1];
					}
					else
					{
						senderBareJid = senderFullJid;
						senderResource = "unknown";
					}
							
					
					String textMessage;
					NodeList bodyList = receivedElement
							.getElementsByTagName("body");
					if (bodyList.getLength() > 0) {
						textMessage = bodyList.item(0).getTextContent();
						Date receivedDate = new Date();
						String alias = "";
						if (this.session.getContact(senderBareJid) != null)
							alias = this.session.getContact(senderBareJid).getAlias();
						
						Message message = new Message(new Contact(
								senderBareJid, alias), null, textMessage,
								receivedDate);
						session.getConversation(new Contact(senderBareJid, ""))
								.addIncomingMessage(message, senderResource);
					} else
						textMessage = "";

				} 
				else if (tagName.equals("presence")) 
				{
					String senderFullJid = receivedElement.getAttribute("from");

					String senderBareJid;
					String senderResource;
					
					if (senderFullJid.contains("/"))
					{
						senderBareJid = senderFullJid.split("/")[0];
						senderResource = senderFullJid.split("/")[1];
					}
					else
					{
						senderBareJid = senderFullJid;
						senderResource = "unknown";
					}
						

					String type = receivedElement.getAttribute("type");
					
					if (type.equals("subscribe"))
					{
						this.session.handleReceivedSubscriptionRequest(senderBareJid);
					}
					if (type.equals("unsubscribe"))
					{
						Contact thisContact = this.session.getContact(senderBareJid);
						if (thisContact != null)
							this.session.removeContact(thisContact);
						
					}
					if (type.equals("unsubscribed"))
					{
						Contact thisContact = this.session.getContact(senderBareJid);
						if (thisContact != null)
							this.session.removeContact(thisContact);
					}

					String showStatus;
					NodeList showList = receivedElement
							.getElementsByTagName("show");
					if (showList.getLength() > 0)
						showStatus = showList.item(0).getTextContent();
					else
						showStatus = "";

					String friendlyName;
					NodeList friendlyList = receivedElement
							.getElementsByTagName("status");
					if (friendlyList.getLength() > 0)
						friendlyName = friendlyList.item(0).getTextContent();
					else
						friendlyName = null;

					Contact contact = session.getContact(senderBareJid);
					if (contact != null) {
						if (showStatus.length() == 0) {
							if (type.length() == 0)
								contact.setStatus(senderResource, 
										ContactStatus.AVAILABLE);
							else if (type.equals("unavailable"))
								contact.setStatus(senderResource, 
										ContactStatus.OFFLINE);
						}
						if (showStatus.equals("chat")) {
							contact.setStatus(senderResource,
									ContactStatus.CHAT);
							if (type.equals("unavailable"))
								contact.setStatus(senderResource, 
										ContactStatus.OFFLINE);
						}
						if (showStatus.equals("dnd")) {
							contact.setStatus(senderResource, 
									ContactStatus.DND);
							if (type.equals("unavailable"))
								contact.setStatus(senderResource, 
										ContactStatus.OFFLINE);
						}
						if (showStatus.equals("away")) {
							contact.setStatus(senderResource,
									ContactStatus.AWAY);
							if (type.equals("unavailable"))
								contact.setStatus(senderResource, 
										ContactStatus.OFFLINE);
						}
						if (showStatus.equals("xa")) {
							contact.setStatus(senderResource, 
									ContactStatus.XA);
							if (type.equals("unavailable"))
								contact.setStatus(senderResource, 
										ContactStatus.OFFLINE);
						}

					}

				}
				else if (tagName.equals("iq"))
				// info from server
				{
					String id = receivedElement.getAttribute("id");
					String type = receivedElement.getAttribute("type");

					

					if (id.equals("contactRequest") && type.equals("result")) 
					{
						// list of contacts is returned
						NodeList contactList = receivedElement
								.getElementsByTagName("item");

						for (int i = 0; i < contactList.getLength(); i++) {

							// extract info
							Element item = (Element) contactList.item(i);
							String bareJid = item.getAttribute("jid");
							String alias = item.getAttribute("name");
							String subscription = item.getAttribute("subscription");
							boolean isSubscribed = subscription.equals("both")
												|| subscription.equals("to")
												|| subscription.equals("from");
							
							boolean isNew = (session.getContact(bareJid) == null);
							// check current contact and whether approved
							if (isSubscribed && isNew) {
								Contact receivedContact = new Contact(bareJid,
										alias);
								session.addReceivedContact(receivedContact);
							}
						}

					}

					// roster push 
					else if (type.equals("set")) 
					{
						NodeList contactList = receivedElement.getElementsByTagName("item");
						Element item = (Element) contactList.item(0);
						String bareJid = item.getAttribute("jid");
						String alias = item.getAttribute("name");
						String subscription = item.getAttribute("subscription");
						
						//roster push from removeContact
						if (subscription.equals("remove")) {
							Contact contact = session.getContact(bareJid);
							session.removeContact(contact);
						}
						
						// roster push add contact
						else if (!subscription.isEmpty())
						{
							Contact newContact = new Contact(bareJid, alias);
							boolean isNew = (session.getContact(bareJid) == null);
							boolean hasNewName = false;
							
							if (isNew) {
								session.addReceivedContact(newContact);
							}
							else{
								String currentAlias = session.getContact(bareJid).getAlias();
								if (!currentAlias.equals(alias)){
	
									session.getContact(bareJid).setAlias(alias);
								}
									
							}
						}
					}
					

				} 
				else 
				{
				}

				receivedElement = this.xmppReader.readSecondLevelElement();

			}
			return;

		} catch (XMPPException e) {
			this.session.processReceivedException(e);
		}

	}

	/**
	 * Closes the connection. If the connection was already closed before this
	 * method is called nothing is done, otherwise sends all necessary closing
	 * data and waits for the server to send the closing data as well. Once this
	 * happens the socket connection is closed. This method does not throw any
	 * exception, choosing instead to ignore them. However, even if an exception
	 * happens while sending the final data, the socket connection will be
	 * closed.
	 */
	public synchronized void closeConnection() {
		/*
		 * not sure for this part, how to check whether connection is already
		 * done
		 */
		if (xmppReader.isDocumentComplete()) {
			return;
		}

		/* send 'unavailable presence' */
		Element outMessage;
		outMessage = xmppWriter.createElement("presence");
		outMessage.setAttribute("type", "unavailable");
		try {
			xmppWriter.writeIndividualElement(outMessage);
			xmppWriter.writeCloseTagRootElement();
			xmppReader.waitForCloseDocument();
		} catch (XMPPException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		/* closing socket */
		try {
			socket.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Sends a request for the contact list. The result is not expected to be
	 * received in this function, but it should come in a message that will be
	 * handled by the listening process.
	 * 
	 * @throws XMPPException
	 *             If there was a problem sending the request.
	 */
	public void sendRequestForContactList() throws XMPPException {

		try {
			Element request = xmppWriter.createElement("iq");
			request.setAttribute("from", session.getUserJid());
			request.setAttribute("id", "contactRequest");
			request.setAttribute("type", "get");

			Element query = xmppWriter.createElement("query");
			query.setAttribute("xmlns", "jabber:iq:roster");
			request.appendChild(query);

			xmppWriter.writeIndividualElement(request);

		} catch (XMPPException e) {
			throw new XMPPException("Fail to send contacts requet", e);
		}
	}

	/**
	 * Sends an updated status information to the server, based on the status
	 * currently attributed to the session.
	 * 
	 * @throws XMPPException
	 *             If there was a problem sending the status.
	 */
	public void sendCurrentStatus() throws XMPPException {
		sendStatus(session.getCurrentStatus());
	}

	/**
	 * Sends a specific status information to the server.
	 * 
	 * @param status
	 *            Status to send to the server.
	 * @throws XMPPException
	 *             If there was a problem sending the status.
	 */
	private void sendStatus(ContactStatus status) throws XMPPException {

		if (status.isOnline()) 
		{
			Element presenceElement = this.xmppWriter.createElement("presence");
			Element show = this.xmppWriter.createElement("show");

			if (status == ContactStatus.AVAILABLE)
				this.xmppWriter.writeIndividualElement(presenceElement);
			else 
			{
				if (status == ContactStatus.AWAY)
					show.setTextContent("away");
				if (status == ContactStatus.CHAT)
					show.setTextContent("chat");
				if (status == ContactStatus.DND)
					show.setTextContent("dnd");
				if (status == ContactStatus.XA)
					show.setTextContent("xa");
				presenceElement.appendChild(show);
				this.xmppWriter.writeIndividualElement(presenceElement);
			}
		} 
		else 
		{
			Element presenceElement = this.xmppWriter.createElement("presence");
			presenceElement.setAttribute("type", "unavailable");
			this.xmppWriter.writeIndividualElement(presenceElement);
		}
	}

	/**
	 * Sends a request that a new contact be added to the list of contacts.
	 * Additionally, requests authorization from that contact to receive updates
	 * any time the contact changes its status. This function does not add the
	 * user to the local list of contacts, which happens at the listening
	 * process once the server sends an update to the list of contacts as a
	 * result of this request.
	 * 
	 * @param contact
	 *            Contact that should be requested.
	 * @throws XMPPException
	 *             If there is a problem sending the request.
	 */
	public void sendNewContactRequest(Contact contact) throws XMPPException {

		// send addNewContactRequest
		Element request = xmppWriter.createElement("iq");
		request.setAttribute("from", session.getUserJid());
		request.setAttribute("id", "newContactRequest");
		request.setAttribute("type", "set");

		Element query = xmppWriter.createElement("query");
		query.setAttribute("xmlns", "jabber:iq:roster");

		Element item = xmppWriter.createElement("item");
		item.setAttribute("jid", contact.getBareJid());
		item.setAttribute("name", contact.getAlias());

		query.appendChild(item);
		request.appendChild(query);
		// write to socket
		xmppWriter.writeIndividualElement(request);

		// send subscription request
		request = xmppWriter.createElement("presence");
		request.setAttribute("to", contact.getBareJid());
		request.setAttribute("id", "subscriptionRequest");
		request.setAttribute("type", "subscribe");
		xmppWriter.writeIndividualElement(request);

	}

	/**
	 * Sends a response message to a contact that requested authorization to
	 * receive updates when the local user changes its status.
	 * 
	 * @param jid
	 *            Jabber ID of the contact that requested authorization.
	 * @param accepted
	 *            <code>true</code> if the request was accepted by the user,
	 *            <code>false</code> otherwise.
	 * @throws XMPPException
	 *             If there was an error sending the response.
	 */
	public void respondContactRequest(String jid, boolean accepted)
			throws XMPPException {

		Element response = xmppWriter.createElement("presence");
		response.setAttribute("id", "respondContactRequest");
		response.setAttribute("to", jid);
		if (accepted){
			response.setAttribute("type", "subscribed");
		}else{
			response.setAttribute("type", "unsubscribed");
		}
		xmppWriter.writeIndividualElement(response);
	}

	/**
	 * Request that the server remove a specific contact from the list of
	 * contacts. Additionally, requests that no further status updates be sent
	 * regarding that contact, as well as that no further status updates about
	 * the local user be sent to that contact. This function does not remove the
	 * user from the local list of contacts, which happens at the listening
	 * process once the server sends an update to the list of contacts as a
	 * result of this request.
	 * 
	 * @param contact
	 *            Contact to be removed from the list of contacts.
	 * @throws XMPPException
	 *             If there was an error sending the request.
	 */
	public void removeAndUnsubscribeContact(Contact contact)
			throws XMPPException {

		// send removeRequest
				Element request = xmppWriter.createElement("iq");
				request.setAttribute("from", session.getUserJid());
				request.setAttribute("id", "removeContactRequest");
				request.setAttribute("type", "set");

				Element query = xmppWriter.createElement("query");
				query.setAttribute("xmlns", "jabber:iq:roster");

				Element item = xmppWriter.createElement("item");
				item.setAttribute("jid", contact.getBareJid());
				item.setAttribute("subscription", "remove");

				query.appendChild(item);
				request.appendChild(query);
				// write to socket
				xmppWriter.writeIndividualElement(request);

				// send subscription cancellation request
				request = xmppWriter.createElement("presence");
				request.setAttribute("to", contact.getBareJid());
				request.setAttribute("id", "subcriptionCancellationRequest");
				request.setAttribute("type", "unsubscribed");
				xmppWriter.writeIndividualElement(request);
				
				//send unsubcribeRequest
				request = xmppWriter.createElement("presence");
				request.setAttribute("to", contact.getBareJid());
				request.setAttribute("id", "unsubcribeRequest");
				request.setAttribute("type", "unsubscribe");
				xmppWriter.writeIndividualElement(request);
	}

	/**
	 * Send a chat message to a specific contact.
	 * 
	 * @param message
	 *            Message to be sent.
	 * @throws XMPPException
	 *             If there was a problem sending the message.
	 */
	public void sendMessage(Message message) throws XMPPException {

		String toWhom = message.getTo().getFullJid();
		String fromWhom = session.getUserJid();
		Element messageElement = this.xmppWriter.createElement("message");
		messageElement.setAttribute("from", fromWhom);
		messageElement.setAttribute("to", toWhom);
		messageElement.setAttribute("type", "chat");
		messageElement.setAttribute("xml:lang", "en");

		Element bodyElement = this.xmppWriter.createElement("body");
		bodyElement.setTextContent(message.getTextMessage());

		messageElement.appendChild(bodyElement);

		this.xmppWriter.writeIndividualElement(messageElement);

	}
}
